package org.hawk.gwt.ppc.utils;


/**
 * Ant-style implementation of the {@link Pattern}
 * interface.
 * @author alex.bereznevatiy@gmail.com
 */
public class AntPattern implements Pattern {
	private static final char ASTERISK = '*';
	private static final String GLOBAL_MATCHER = StringUtils.create(ASTERISK,ASTERISK);
	private static final String LOCAL_MATCHER = StringUtils.create(ASTERISK);
	private static final char PATH_SEPARATOR = '.';
	private String[] regex;
	
	public AntPattern(String regex){
		this(tokenize(prepare(regex)));
	}
	
	private static CharSequence prepare(String regex) {
		if(regex==null||regex.length()==0){
			return regex;
		}
		StringBuilder result = new StringBuilder();
		String[] tmp =  StringUtils.split(regex, GLOBAL_MATCHER);
		String prev = tmp[0];
		if(tmp.length==1){
			return prev;
		}
		result.append(prev);
		for(int i=1;i<tmp.length;i++){
			String str = tmp[i];
			if(prev.length()>0&&
				prev.charAt(prev.length()-1)!=PATH_SEPARATOR){
				result.append(ASTERISK);
				result.append(PATH_SEPARATOR);
			}
			result.append(GLOBAL_MATCHER);
			if(str.length()>0&&str.charAt(0)!=PATH_SEPARATOR){
				result.append(PATH_SEPARATOR);
				result.append(ASTERISK);
			}
			result.append(str);
			prev = str;
		}
		return result.toString();
	}

	private AntPattern(String[] regex){
		this.regex = regex;
	}
	
	public boolean match(String resource) {
		return getRelevance(resource)>0;
	}

	private int getRelevance(String mapping) {
		return calculateRelevance(regex,tokenize(mapping),0,0,0);
	}

	private int calculateRelevance(
			String[] mapping, String[] url, 
			int mPos,int uPos,int result) {
		// both mapping and url reach theirs ends
		if(mapping.length<=mPos&&
				url.length<=uPos){
			return result;
		}
		// only one (either mapping or url) reach its end 
		if(mapping.length<=mPos||
				url.length<=uPos){
			return 0;
		}
		// long path match - complex calculation
		if(mapping[mPos].equals(GLOBAL_MATCHER)){
			return calculateMultiwayRelevance(mapping,url,mPos+1,uPos,result);
		}
		// short path match
		if(mapping[mPos].equals(LOCAL_MATCHER)){
			return calculateRelevance(mapping,url,mPos+1,uPos+1,result+2);
		}
		// complex pattern match
		if(mapping[mPos].indexOf(ASTERISK)>=0){
			if(!prefixMatch(mapping[mPos],url[uPos]))return 0;
			return calculateRelevance(mapping,url,mPos+1,uPos+1,result+4);
		}
		// exact match
		if(mapping[mPos].equals(url[uPos])){
			if(mPos==(mapping.length-1)&&uPos==(url.length-1)){
				// adds some benefit to suffix mapping comparative 
				// to prefix one of the same length
				result+=1;
			}
			return calculateRelevance(mapping,url,mPos+1,uPos+1,result+8);
		}
		return 0;//unable to match anything
	}

	private int calculateMultiwayRelevance(String[] mapping, String[] url,
			int mPos, int uPos, int result) {
		result += 1;
		if(mapping.length<=mPos)return result;
		while(url.length>uPos){
			int rel = calculateRelevance(mapping,url,mPos,uPos,result);
			if(rel>0)return rel;
			uPos++;
		}
		return 0;
	}
	
	private boolean prefixMatch(String pattern, String test) {
		return prefixMatch(pattern,test,0,0);
	}
	
	private boolean prefixMatch(String pattern, String test,int i,int j) {
		final int LENGTH1 = pattern.length();
		final int LENGTH2 = test.length();
		while(i<LENGTH1&&j<LENGTH2){
			char p = pattern.charAt(i);
			char t = test.charAt(j);
			if(p=='*'){
				i++;
				if(i>=LENGTH1){
					return true;
				}
				boolean rez = false;
				while(j<LENGTH2&&!rez){
					rez = prefixMatch(pattern,test,i,j++);
				}
				return rez;
			}
			if(p!=t){
				return false;
			}
			j++;i++;
		}
		while(i<LENGTH1&&pattern.charAt(i)=='*')i++;
		return i>=LENGTH1&&j>=LENGTH2;
	}

	private static String[] tokenize(final CharSequence pattern){
		String[] result = new String[calculateResultLength(pattern)];
		final int LENGTH = pattern.length();
		char[] buf = new char[LENGTH];
		int j=0;int p=0;
		for(int i=0;i<LENGTH;i++){
			final char c = pattern.charAt(i);
			if(c==PATH_SEPARATOR){
				result[p++] = new String(buf,0,j);
				j=0;
				continue;
			}
			buf[j++] = c;
		}
		result[p] = new String(buf,0,j);
		return result;
	}
	
	private static int calculateResultLength(final CharSequence pattern){
		int rez = 1;
		final int LENGTH = pattern.length();
		for(int i=0;i<LENGTH;i++){
			if(pattern.charAt(i)==PATH_SEPARATOR){
				rez++;
			}
		}
		return rez;
	}
}
